package com.youlu.campus.web.order.service.pay;

import com.alibaba.fastjson.JSON;
import com.github.wxpay.sdk.WXPayConstants;

import com.youlu.campus.common.utils.RedisKeyUtil;
import com.youlu.campus.component.SmsUtil;
import com.youlu.campus.entity.OrderInfo;
import com.youlu.campus.entity.SmsTemplate;
import com.youlu.campus.service.base.BaseService;
import com.youlu.campus.service.card.TsCardService;
import com.youlu.campus.service.course.UserCourseOrderService;
import com.youlu.campus.service.kos.UserKosService;
import com.youlu.campus.service.order.ContestQaOrderService;
import com.youlu.campus.service.pay.UserPurchaseCaService;
import com.youlu.campus.service.sms.SmsTemplateService;
import com.youlu.campus.service.utils.HttpClientUtils;
import com.youlu.campus.service.utils.WXPayUtil;
import com.youlu.campus.service.wechatpay.WechatPayCacheService;
import com.youlu.campus.web.common.utils.ParseXMLUtils;
import com.youlu.campus.web.common.utils.PayUtil;
import com.youlu.campus.web.data.english.CompetitionFeeService;
import com.youlu.campus.web.order.dto.WxPayRequest;
import com.youlu.campus.web.order.entity.PayNotifyResultPO;
import com.youlu.campus.web.order.service.DigitalCaOrderService;
import com.youlu.campus.web.order.service.OrderService;
import com.yuelin.infrastructure.quantum.common.QResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author zhuhuaiqi
 * @date 2020/12/10 14:49
 */
@Service
@Slf4j
public class PayService extends BaseService {

    @Autowired
    private WxPayConfig wxPayConfig;

    @Autowired
    private OrderService orderService;
    @Autowired
    private WechatPayCacheService WechatPayCacheService;

    @Autowired
    private CompetitionFeeService competitionFeeService;

    @Autowired
    private UserCourseOrderService userCourseOrderService;

    @Autowired
    private DigitalCaOrderService digitalCaOrderService;

    @Autowired
    private UserKosService userKosService;

    @Autowired
    private TsCardService tsCardService;

    @Lazy
    @Autowired
    private ContestQaOrderService contestQaOrderService;
    @Autowired
    private UserPurchaseCaService userPurchaseCaService;

    @Autowired
    private SmsTemplateService smsTemplateService;

    @Autowired
    private SmsUtil smsUtil;

    public QResult<Map<String, String>> unifiedOrder(WxPayRequest request) {
        log.info("发起支付 {}", JSON.toJSONString(request));
        try {
            log.info("使用配置 APP ID：{}，商户号：{} 正在发起支付, 订单:[{}], 金额:{}"
                    , request.getAppId(), request.getMerchantId(), request.getOrderNo(), request.getTotalFee());

            String nonceStr = request.getNonceStr();
            Map<String, Object> param = new HashMap<>();
            String xmlRequest = buildParamXml(request, nonceStr, param);
            String nullKeys = param.keySet().stream().filter(k -> param.get(k) == null)
                    .collect(Collectors.joining(","));
            if (!nullKeys.isEmpty()) {
                return QResult.fail("请求参数中有为空:" + nullKeys);
            }
            log.info("unifiedOrder :{} ", xmlRequest);
            PayRecord pr = new PayRecord();
            BeanUtils.copyProperties(request, pr);
            pr.setCreatedTime(new Date());
            mongoTemplate.save(pr);
            String uniUrl = "";
            switch (request.getPlatform()) {
                case "h5":
                    uniUrl = wxPayConfig.getUniUrl();
                    uniUrl = uniUrl.replace("{AppId}", request.getAppId());
                    uniUrl = uniUrl.replace("{AccessToken}", request.getAccessToken());
                    break;
                case "wechat-h5":
                    uniUrl = wxPayConfig.getWxUniUrl();
                    break;
                case "qq":
                    uniUrl = wxPayConfig.getUniUrl();
                    uniUrl = uniUrl.replace("{AppId}", request.getAppId());
                    uniUrl = uniUrl.replace("{AccessToken}", request.getAccessToken());
                    break;
                //TODO:dengbo
                case "wechat-mini":
                    uniUrl = wxPayConfig.getWxUniUrl();
                    break;
                default:
                    break;
            }
            log.info(":>>> 统一下单:{},{}", uniUrl, xmlRequest);
            ResponseEntity<String> response = HttpClientUtils.getPayRestTemplate().postForEntity(uniUrl, xmlRequest, String.class);
            String body = response.getBody();
            log.info(request.getOrderNo() + " result response:{}", body);
            UnifiedOrderResult result = (UnifiedOrderResult) ParseXMLUtils.beginXMLParse(body, UnifiedOrderResult.class);

            log.info(":>>> 统一支付结果:{}", JSON.toJSONString(result));
            if (result == null) {

                return QResult.fail("生成预支付单失败。");
            }
            pr.setResult(BeanMap.create(result));
            mongoTemplate.save(pr);
            if (UnifiedOrderResult.SUCCESS.equals(result.getReturn_code())) {
                //支付成功
                if (UnifiedOrderResult.SUCCESS.equals(result.getResult_code())) {
                    Map<String, String> map = new HashMap();
                    String timeStampStr = request.getTimeStampStr1();
                    map.put("timeStamp", timeStampStr);
                    map.put("url", result.getMweb_url());
                    map.put("referer", wxPayConfig.getReferer());
                    map.put("nonceStr", nonceStr);
//                    if ("wechat-h5".equals(request.getPlatform())) {
                    map.put("timeStamp", timeStampStr);
                    map.put("nonceStr", nonceStr);
                    String packageStr = "";
                    if (StringUtils.isNotBlank(result.getPrepay_id())) {
                        map.put("package", "prepay_id=" + result.getPrepay_id());
                        packageStr = "prepay_id=" + result.getPrepay_id();
                    }
                    map.put("signType", "MD5");
                    if (StringUtils.isNotBlank(result.getMweb_url())) {
                        map.put("mweb_url", result.getMweb_url());
                        map.put("trade_type", "MWEB");
                    }

                    HashMap<String, String> toDo = new HashMap<>();
                    toDo.put("appId", request.getAppId());
                    toDo.put("nonceStr", nonceStr);
                    toDo.put("package", packageStr);
                    toDo.put("signType", "MD5");
                    toDo.put("timeStamp", timeStampStr);

//                    }
                    try {
                        String paySignStr = WXPayUtil.generateSignature(toDo, request.getKey(), WXPayConstants.SignType.MD5);
                        //注意这里的参数要根据ASCII码 排序
//                        String paySignStr = PayUtil.sign(paySign, wxPayConfig.getKey(), "utf-8").toUpperCase();
//                        String paySignStr = PayUtil.sign(paySign, wxPayConfig.getKey(), "utf-8").toLowerCase();
                        map.put("paySign", paySignStr);
                        map.put("timeStamp", timeStampStr);
                        map.put("orderNo", request.getOrderNo());
                        map.put("totalFee", "" + request.getTotalFee());
                        return QResult.success(map);
                    } catch (Exception e) {
                        log.error("支付成功后，签名错误", e);
                        return QResult.fail("支付异常，请联系客服。[0110]");
                    }
                } else {
                    return QResult.fail("支付失败，" + result.getErr_code() + ":" + result.getErr_code_des());
                }
            } else {
                return QResult.fail("支付出错，" + result.getReturn_msg());
            }
        } catch (Exception e) {
            String msg = "生成预支付单异常";
            log.error(msg + request.getOrderNo(), e);
            return QResult.fail("生成预支付单异常");
        }
    }

    public String payNotify(String content) {
        log.info(":>>> 开始处理支付回调,payNotify content {}", content);
        try {
            WxNotifyResult notifyResult = (WxNotifyResult) ParseXMLUtils.beginXMLParse(content, WxNotifyResult.class);
            if (Objects.isNull(notifyResult)) {
                log.error(":>>> 微信支付参数为空");
                return WxNotifyResult.FAIL;
            }
            //校验签名
            Map<String, Object> params = new HashMap<>(BeanMap.create(notifyResult));
            params.remove("sign");
            String prestr = PayUtil.createLink(params);
            //MD5运算生成签名，这里是第一次签名，用于调用统一下单接口
            String sign = PayUtil.sign(prestr, wxPayConfig.getKey(), "utf-8").toUpperCase();
            if (!notifyResult.getSign().equalsIgnoreCase(sign)) {
                log.error("签名校验不通过！notifyResult {}", notifyResult);
//                return QQNotifyResult.FAIL;
            }
            //支付状态判断
            if (!notifyResult.getResult_code().equalsIgnoreCase(WxNotifyResult.SUCCESS)) {
                log.error(":>>> 微信支付不是成功状态");
                return WxNotifyResult.FAIL;
            }
            PayNotifyResultPO resultPO = handlerNotifyResult(notifyResult);
            PayNotify msg = new PayNotify();
            msg.setOrderNo(notifyResult.getOut_trade_no());
            msg.setTotalFee(notifyResult.getTotal_fee().toString());
            msg.setTransaction_id(notifyResult.getTransaction_id());
            msg.setOriginContent(content);
            if (msg.getOrderNo().startsWith("E")) {
                competitionFeeService.notifyPay(notifyResult.getOut_trade_no(), notifyResult.getTransaction_id());
            } else if (msg.getOrderNo().startsWith("WXLTKCSP")) {
                userCourseOrderService.dealOrderPaySuccess(msg.getOrderNo(), notifyResult.getTransaction_id());
            } else if (msg.getOrderNo().startsWith("CARD")) {
                tsCardService.dealOrderPaySuccess(msg.getOrderNo(), notifyResult.getTransaction_id());
            } else if (msg.getOrderNo().startsWith("WXLTKOS")) {
                userKosService.dealOrderPayNotity(msg.getOrderNo(), notifyResult.getTransaction_id());
            } else if (msg.getOrderNo().startsWith("D")) {//电子证书
                digitalCaOrderService.notifyPay(msg.getOrderNo(), notifyResult.getTransaction_id());
            } else if (msg.getOrderNo().startsWith("CONTEST")) {//竞赛订单
                contestQaOrderService.handlePayNotifyOrder(msg.getOrderNo(), notifyResult.getTransaction_id());
            } else {
                //调用具体service
                orderService.orderPayNotify(msg);
                WechatPayCacheService.updateStatus(msg.getOrderNo(), Boolean.TRUE);
                userPurchaseCaService.updateItemBuyRecord(msg.getOrderNo(), new Date());
                userPurchaseCaService.updateCategoryBuyRecord(msg.getOrderNo(), new Date());
                contestQaOrderService.handleH5OrderPayNotifyOrder(msg.getOrderNo(), notifyResult.getTransaction_id());
            }
            resultPO.setUpdatedTime(new Date());
            resultPO.setStatus("pushed");
            mongoTemplate.save(resultPO);
            //发送短信
            sendPaymentSms(msg);
            return WxNotifyResult.SUCCESS;
        } catch (Exception e) {
            log.error("payNotify error {}", content, e);
            return WxNotifyResult.FAIL;
        }
    }

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 发送支付完成 短信
     *
     * @param msg
     */
    private void sendPaymentSms(PayNotify msg) {
        try {
            log.info("sendPaymentSms 发送支付完成 短信 orderNo:{}", msg.getOrderNo());
            Query query = new Query();
            query.addCriteria(Criteria.where("orderNo").is(msg.getOrderNo()));
            OrderInfo orderInfo = this.mongoTemplate.findOne(query, OrderInfo.class);
            if (Objects.isNull(orderInfo) || !orderInfo.getPaymentSmsEnabled()) {
                log.info("sendPaymentSms 订单未开启支付完成短信 orderNo:{}", msg.getOrderNo(), orderInfo);
                return;
            }
            String key = RedisKeyUtil.getOrderPaymentSms(orderInfo.getAccountInfoId(), orderInfo.getActivityId());
            Long smsNum = redisTemplate.opsForValue().increment(key, 1);
            log.info("sendPaymentSms 发送支付完成短信 是否重复 orderNo:{},smsNum:{}", msg.getOrderNo(), smsNum);
            if (smsNum > 1) {
                return;
            }
            SmsTemplate smsTemplate = smsTemplateService.findBySignAndType("赞我", "payment-sms");
            if (Objects.isNull(smsTemplate)) {
                log.error("sendPaymentSms 短信模版配置错误 smsTemplate is null");
                return;
            }
            //发送短信变量
            String[] param = {orderInfo.getName(), orderInfo.getSmsActivityName()};
            //生成六位随机码
            smsUtil.sendSmsWithContent("赞我", orderInfo.getMobile(), smsTemplate.getTempalteId(), param);
        } catch (Exception e) {
            log.error("error :{}", e.getMessage(), e);
        }
    }

    private String buildParamXml(WxPayRequest request, String nonceStr, Map<String, Object> param) {
        StringBuffer bf = new StringBuffer();

        bf.append("<xml>");
        if ("qq".equals(request.getPlatform())) {
            bf = appendElement(bf, "appid", request.getWechatAppId(), param);
        } else {
            bf = appendElement(bf, "appid", request.getAppId(), param);
        }
//        bf = appendElement(bf, "appid", request.getAppId(), param);

        bf = appendElement(bf, "mch_id", request.getMerchantId(), param);

        bf = appendElement(bf, "nonce_str", nonceStr, param);

        bf = appendElement(bf, "body", request.getDesc(), param);

        bf = appendElement(bf, "out_trade_no", request.getOrderNo(), param);

        bf = appendElement(bf, "fee_type", "CNY", param);

        bf = appendElement(bf, "total_fee", request.getTotalFee(), param);

        String profitSharingStr = (request.getProfitSharing() == null || !request.getProfitSharing()) ? "N" : "Y";
        bf = appendElement(bf, "profit_sharing", profitSharingStr, param);

        bf = appendElement(bf, "spbill_create_ip", request.getClientIp(), param);
        if ("wechat-h5".equals(request.getPlatform())) {
            bf = appendElement(bf, "notify_url", wxPayConfig.getPayWechatNotifyUrl(), param);
        } else {
            bf = appendElement(bf, "notify_url", wxPayConfig.getPayQQNotifyUrl(), param);
        }
        switch (request.getPlatform()) {
            case "h5":
                bf = appendElement(bf, "trade_type", "MWEB", param);
                Map<String, Object> h5InfoMap = new HashMap<>(1);
                Map<String, String> h5Map = new HashMap<>(3);
                h5Map.put("type", "Wap");
                h5Map.put("wap_url", "https://wechat-h5-test.gqgood.com/h5/index.html?#/pages/activity-detial?activityId=60f8c919ff3415216ea74e82&appId=wx425adcb3303f8896&theme=cornflowerblue");
                h5Map.put("wap_name", "梦想跳动");
                h5InfoMap.put("h5_info", h5Map);
                bf = appendElement(bf, "scene_info", JSON.toJSONString(h5InfoMap), param);
                break;
            case "wechat-h5":
                bf = appendElement(bf, "trade_type", "JSAPI", param);
                if (StringUtils.isNotBlank(request.getOpenId())) {
                    bf = appendElement(bf, "openid", request.getOpenId(), param);
                    log.info(":>>> 设置OpenId");
                }
                break;
            case "qq":
                bf = appendElement(bf, "trade_type", "MWEB", param);
//                if (StringUtils.isNotBlank(request.getOpenId())) {
//                    bf = appendElement(bf, "openid", request.getOpenId(), param);
//                    log.info(":>>> 设置OpenId");
//                }
                break;
            default:
                break;
        }
        String prestr = PayUtil.createLink(param);
        //MD5运算生成签名，这里是第一次签名，用于调用统一下单接口
        String sign = PayUtil.sign(prestr, request.getKey(), "utf-8").toUpperCase();

        bf = appendElement(bf, "sign", sign, param);

        bf.append("</xml>");

        return bf.toString();
    }

    private StringBuffer appendElement(StringBuffer bf, String elementKey, Object elementValue, Map<String, Object> param) {
        bf.append("<" + elementKey + "><![CDATA[");
        bf.append(elementValue);
        bf.append("]]></" + elementKey + ">");
        if (param != null) {
            param.put(elementKey, elementValue);
        }
        return bf;
    }

    private PayNotifyResultPO handlerNotifyResult(WxNotifyResult notifyResult) {
        Query query = new Query();
        query.addCriteria(Criteria.where("orderNo").is(notifyResult.getOut_trade_no()));
        PayNotifyResultPO resultPO = mongoTemplate.findOne(query, PayNotifyResultPO.class);
        if (resultPO == null) {
            resultPO = new PayNotifyResultPO();
            resultPO.setWxResult(notifyResult);
            resultPO.setCreatedTime(new Date());
            resultPO.setExeCount(1);
            resultPO.setOrderNo(notifyResult.getOut_trade_no());
            //PayNotifyResultEnum
            resultPO.setStatus("received");
        } else {
            resultPO.setExeCount(resultPO.getExeCount() + 1);
            resultPO.setUpdatedTime(new Date());
        }
        mongoTemplate.save(resultPO);
        return resultPO;
    }

}
